package net.zoneland.zrdp.framework.aspectj;

import java.lang.reflect.Method;
import java.util.Objects;

import net.zoneland.zrdp.common.core.redis.RedisService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import net.zoneland.zrdp.common.annotation.RateLimiter;
import net.zoneland.zrdp.common.enums.LimitType;
import net.zoneland.zrdp.common.exception.ServiceException;
import net.zoneland.zrdp.common.utils.ip.IpUtils;

import javax.annotation.Resource;

/**
 * 限流处理
 *
 * @author zonevue
 */
@Aspect
@Component
public class RateLimiterAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterAspect.class);

    private RedisScript<Long> limitScript;

    @Resource
    private RedisService redisService;
    @Resource
    public void setLimitScript(RedisScript<Long> limitScript)
    {
        this.limitScript = limitScript;
    }

    @Before("@annotation(rateLimiter)")
    public void doBefore(final JoinPoint point, final RateLimiter rateLimiter) {
        final int time = rateLimiter.time();
        final int count = rateLimiter.count();

        final String combineKey = getCombineKey(rateLimiter, point);
        try {
            final Long number = redisService.getCacheNumber(limitScript, combineKey, count, time);
            if (Objects.isNull(number) || number.intValue() > count) {
                throw new ServiceException("访问过于频繁，请稍候再试");
            }
            LOGGER.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
        } catch (final ServiceException e) {
            throw e;
        } catch (final Exception e) {
            throw new ServiceException("服务器限流异常，请稍候再试", e);
        }
    }

    public String getCombineKey(final RateLimiter rateLimiter, final JoinPoint point) {
        final StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            stringBuffer.append(IpUtils.getIpAddr()).append("-");
        }
        final MethodSignature signature = (MethodSignature) point.getSignature();
        final Method method = signature.getMethod();
        final Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
        return stringBuffer.toString();
    }
}
